package tclp.capture;

import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.*;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import net.sourceforge.javaocr.ocrPlugins.mseOCR.TrainingImage;
import tclp.simpleocr.OCRWrapper;

/**
 *
 * @author Absatz Leoš
 */
public class CaptureThread extends Thread {

    private boolean running = false;
    private int x1, y1, x2, y2;
    private HashMap<Character, ArrayList<TrainingImage>> trainingImages;
    private short[] invertTable;
    private File frapsDir;
    private File outputFile;

    public CaptureThread(String screenCoords,
            HashMap<Character, ArrayList<TrainingImage>> trainingImages,
            String frapsPath, String outputFile) {
        this.trainingImages = trainingImages;
        this.frapsDir = new File(frapsPath);
        this.outputFile = new File(outputFile);

        String[] coords = screenCoords.trim().split(",");
        this.x1 = Integer.parseInt(coords[0]);
        this.y1 = Integer.parseInt(coords[1]);
        this.x2 = Integer.parseInt(coords[2]);
        this.y2 = Integer.parseInt(coords[3]);

        if (x2 < x1) {
            int x = x2;
            x2 = x1;
            x1 = x;
        }
        if (y2 < y1) {
            int y = y2;
            y2 = y1;
            y1 = y;
        }

        invertTable = new short[256];
        for (int i = 0; i < 256; i++) {
            invertTable[i] = (short) (255 - i);
        }
    }

    public boolean isRunning() {
        return running;
    }

    public void setRunning(boolean running) {
        this.running = running;
    }

    @Override
    public void run() {
        this.running = true;

        int width = x2 - x1;
        int height = y2 - y1;

        // output file
        BufferedWriter writer = null;
        try {
            writer = new BufferedWriter(new FileWriter(outputFile));
        } catch (IOException ex) {
            System.out.println(ex);
            ex.printStackTrace();
            return;
        }

        // screenshot part with the combat log
        BufferedImage scaledImage = new BufferedImage(width * 2, height * 2, BufferedImage.TYPE_BYTE_GRAY);
        Graphics2D graphics2D = scaledImage.createGraphics();
        graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);

        SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss.SSS");
        OCRWrapper ocrWrapper = new OCRWrapper(trainingImages);
        String[] lastDecodedLines = null;
        String[] decodedLines = null;

        while (running) {
            try {
                File oldestFile = getOldestFile();
                if (oldestFile == null) {
                    continue;
                }

                //load image
                InputStream is = null;
                try {
                    is = new BufferedInputStream(new FileInputStream(oldestFile));
                } catch (FileNotFoundException e) { // not yet available
                    continue;
                }
                BufferedImage image = ImageIO.read(is);
                is.close();
                graphics2D.drawImage(image, 0, 0, width * 2, height * 2, x1, y1, x2, y2, null);

                // invert image colors
                BufferedImage invertedImage = invertImage(scaledImage);

                //filter out images that contain no combat log
                int peak = getHistogramPeak(invertedImage);
                if (peak < 235) { // threshold
                    continue;
                }
                //ImageIO.write(invertedImage, "png", new File("C:\\testoutput"+(i++)+".png"));

                String decodedText = ocrWrapper.run(invertedImage, writer);
                String[] decodedTextLines = translateText(decodedText);
                lastDecodedLines = decodedLines;
                decodedLines = decodedTextLines;
                decodedLines = getNewCombatLogPart(decodedLines, lastDecodedLines);
                for (String line : decodedLines) {
                  Date date = new Date();
                  writer.write(dateFormat.format(date));
                  writer.write(" - ");
                  writer.write(line);
                  writer.write("\n");
                }
                writer.flush();

                System.out.println(dateFormat.format(new Date()) + ": Running capture ...");
                oldestFile.delete();
            } catch (Exception ex) {
                System.out.println(ex);
                ex.printStackTrace();
                System.exit(1);
            }
        }
        graphics2D.dispose();
        try {
            writer.close();
        } catch (IOException ex) {
            System.out.println(ex);
            ex.printStackTrace();
            return;
        }
    }

    private File getOldestFile() {
        File[] availableFiles = frapsDir.listFiles();
        if (availableFiles == null) {
            return null;
        }
        File oldestFile = null;
        for (File file : availableFiles) {
            if (file.isFile() && file.getName().endsWith(".png")) {
                if ((oldestFile == null) || (file.lastModified() < oldestFile.lastModified())) {
                    oldestFile = file;
                }
            }
        }
        return oldestFile;
    }

    private BufferedImage invertImage(BufferedImage src) {
        BufferedImage input = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_INT_RGB);
        Graphics2D g = input.createGraphics();
        g.drawImage(src, 0, 0, src.getWidth(), src.getHeight(), null);
        g.dispose();

        BufferedImage dst = new BufferedImage(input.getWidth(), input.getHeight(), BufferedImage.TYPE_INT_RGB);
        BufferedImageOp invertOp = new LookupOp(new ShortLookupTable(0, invertTable), null);
        return invertOp.filter(input, dst);
    }

    private int getHistogramPeak(BufferedImage image) {
        int[] bins = new int[256];
        for (int i = 0; i < 256; i++) {
            bins[i] = 0;
        }

        Raster raster = image.getRaster();
        for (int i = 0; i < image.getWidth(); i++) {
            for (int j = 0; j < image.getHeight(); j++) {
                bins[ raster.getSample(i, j, 0)]++;
            }
        }

        int peak = 0;
        int peakAmount = bins[0];
        for (int i = 0; i < 256; i++) {
            if (bins[i] > peakAmount) {
                peakAmount = bins[i];
                peak = i;
            }
        }

        return peak;
    }

    private String[] translateText(String text) {
        //translate the text into more parser-friendly form
        Pattern p = Pattern.compile("\\d{2,10}");
        Matcher m = null;

        String[] lines = text.split("\n");
        List<String> result = new ArrayList<String>();
        for (String line : lines) {
            StringBuilder sb = new StringBuilder();
            if (line.startsWith("Hit")) {
                sb.append("Hit");
            } else if (line.startsWith("Crit")) {
                sb.append("Cri");
            } else {
                //@todo damage taken
                // for now discard, later keep damage taken
                //sb.append("Tak");
                continue;
            }
            m = p.matcher(line);
            if (m.find()) {
                sb.append(m.group());
            }
            result.add(sb.toString());
        }

        return result.toArray(new String[result.size()]);
    }

  private String[] getNewCombatLogPart(String[] decodedLines, String[] lastDecodedLines) {
    //@todo ignore first captured combat log (initial state)
    int bestScore = 0;
    int bestOldL = 0;
    for (int oldL = 0; oldL < lastDecodedLines.length; oldL++) {
      int score = 0;
      for (int newL = 0; newL < decodedLines.length; newL++) {
        if ((oldL + newL) >= lastDecodedLines.length) {
          break;
        }
        if (lastDecodedLines[oldL + newL].equals(decodedLines[newL])) {
          score += 100;
        } else {
          score -= 1;
        }
      }

      if (score > bestScore) {
        bestScore = score;
        bestOldL = oldL;
      }
    }

    int keepLines = decodedLines.length - (lastDecodedLines.length - bestOldL);
    String[] result = new String[keepLines];
    System.arraycopy(decodedLines, decodedLines.length - keepLines, result, 0, keepLines);

    return result;
  }
}
